using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using TelnetExpect;
using NUnit.Framework;
using System.Diagnostics;

namespace TelnetTests
{
    public abstract class TestsBase
    {
        public byte[] ExpectedInputNone = { };
        public byte[] ExpectedInput = { 1, 2, 3, 4, 5 };
        public byte[] ExpectedInputByte255At3 = { 1, 2, 3, 255, 4, 5 };
        //
        public byte[] ExpectedResponsesNone = { };
        public byte[] ExpectedResponsesOne = { IAC, WONT, OptionFooOne, };
        public byte[] ExpectedResponsesTwo = { IAC, WONT, OptionFooOne, IAC, WONT, OptionFooTwo, };
        public byte[] ExpectedResponsesOneDont = { IAC, DONT, OptionFooOne, };
        //
        public byte[] ExpectedResponsesEchoWill = { IAC, WILL, OptionCode.Echo, };
        public byte[] ExpectedResponsesEchoDo = { IAC, DO, OptionCode.Echo, };
        public byte[] ExpectedResponsesEchoWillThenWont = {
            IAC, WILL, OptionCode.Echo,
            IAC, WONT, OptionCode.Echo,
        };

        //--------
        protected virtual Stream Modifier_CreateStream(Stream src)
        {
            return src;
        }

        protected virtual void Create_ResultBuffer(int maxLen, out byte[] result, out int offset)
        {
            result = new byte[maxLen];
            offset = 0;
        }

        //--------
        protected void DoTest(byte[] data, byte[] expectedInput)
        {
            DoTest(data, expectedInput, null);
        }

        protected void DoTest(byte[] data, byte[] expectedInput, byte[] expectedResponses)
        {
            DoTest(data, expectedInput, expectedResponses, null, null, null);
        }

        protected void DoTest(byte[] data, byte[] expectedInput, byte[] expectedResponses,
            Action<TelnetStream> modifyStreamBefore,
            TelnetOption[] expectedLocalOptions, TelnetOption[] expectedRemoteOptions)
        {
            DoTest(data, expectedInput, expectedResponses,
            modifyStreamBefore,
            expectedLocalOptions, expectedRemoteOptions,
            null);
        }

        protected virtual void DoTest(byte[] data, byte[] expectedInput, byte[] expectedResponses,
            Action<TelnetStream> modifyStreamBefore,
            TelnetOption[] expectedLocalOptions, TelnetOption[] expectedRemoteOptions,
            byte[] expectedSubNeg)
        {
            //const bool DoOnlyOneReadOperation = false;
            MemoryStream src0 = new MemoryStream(data);
            Stream src = Modifier_CreateStream(src0);
            MemoryStream dst = new MemoryStream();
            Stream conn = new PairStream(src, dst);
            TelnetStream strm = Create_Testee(conn);
            LoggingTelnetOptionHandler logger = new LoggingTelnetOptionHandler();
            strm.AddOptionHandler(logger);
            if (modifyStreamBefore != null) {
                modifyStreamBefore(strm);
            }
            byte[] result;
            int offset;
            Create_ResultBuffer(data.Length, out result, out offset);
            int totalLen = 0;
            while (true) {
                int readLen = strm.Read(result, offset + totalLen, result.Length - offset - totalLen);
                if (readLen == 0) {
                    break;
                }
                totalLen += readLen;
                //if (DoOnlyOneReadOperation) {
                //    break;
                //}
            }
            // Assert
            Assert.IsTrue(strm.IsAtNormalState, "IsAtNormalState");
            Assert_Buffers_AreEqual(expectedInput, result, offset, totalLen, "Input");
            if (expectedResponses != null) {
                byte[] expectedResponses2 = MungeResponseBuffer(expectedResponses, dst);
                //byte[] output = dst.ToArray();
                //Assert_Buffers_AreEqual(expectedResponses, output, 0, output.Length, "Responses");
                Assert_Buffers_AreEqual(expectedResponses2, dst, "Responses");
            }
            //
            const int MaxOption = 256;
            for (int i = 0; i < MaxOption; ++i) {
                TelnetOption option = (TelnetOption)i;
                bool expectedState = false;
                if (expectedLocalOptions != null) {
                    expectedState = -1 != Array.IndexOf<TelnetOption>(expectedLocalOptions, option);
                }
                Assert.AreEqual(expectedState, strm.GetLocalState(option), "Local-Option '" + option + "' state");
            }
            for (int i = 0; i < MaxOption; ++i) {
                TelnetOption option = (TelnetOption)i;
                bool expectedState = false;
                if (expectedRemoteOptions != null) {
                    expectedState = -1 != Array.IndexOf<TelnetOption>(expectedRemoteOptions, option);
                }
                Assert.AreEqual(expectedState, strm.GetRemoteState(option), "Remote-Option '" + option + "' state");
            }
            if (expectedSubNeg != null) {
                logger.DoAssert(new byte[][] { expectedSubNeg });
            }
        }

        protected virtual byte[] MungeResponseBuffer(byte[] expectedResponses, MemoryStream dst)
        {
            return expectedResponses;
        }

        class LoggingTelnetOptionHandler : TelnetOptionHandler
        {
            // Instead add each array to a list??  So can see multiple distinct instances
            List<byte> m_subNegBytes = new List<byte>();
            const string AssertName = "SubNeg bytes";

            public LoggingTelnetOptionHandler ()
            {
            }

            //----
            protected override void HandleSubNegotiation(byte[] data)
            {
                m_subNegBytes.AddRange(data);
            }

            public void DoAssert(byte[][] expectedSubNegInstances)
            {
                if (expectedSubNegInstances.Length == 0) {
                    Assert.AreEqual(0, m_subNegBytes.Count,
                        AssertName + "-- None expected, but had " + m_subNegBytes.Count + " bytes of SubNeg data.");
                }
                if (expectedSubNegInstances.Length != 1) {
                    throw new NotImplementedException("Testing not implemented -- Multiple SubNeg in one test.");
                }
                Assert.AreEqual(expectedSubNegInstances[0], m_subNegBytes.ToArray(), AssertName);
            }
        }//class2

        protected virtual TelnetStream Create_Testee(Stream conn)
        {
#if WONT_DONT
            TelnetBaseStream strm = new TelnetBaseStream(conn);
#elif NO_TO_EVERYTHING
            TelnetBaseStream strm = new TelnetAgreeToSomeStream(conn);
#else
            TelnetStream strm = new TelnetStream(conn);
#endif
            return strm;
        }

        //--------
        public static void Assert_Buffers_AreEqual(byte[] expected, byte[] result, int offset, int count, string name)
        {
            byte[] compare;
            if (count != result.Length || offset != 0) {
                compare = new byte[count];
                Array.Copy(result, offset, compare, 0, count);
            } else {
                compare = result;
            }
            Assert.AreEqual(expected, compare, name);
        }

        public static void Assert_Buffers_AreEqual(byte[] expected, MemoryStream actual, string name)
        {
            byte[] actual2 = actual.ToArray();
            Assert_Buffers_AreEqual(expected, actual2, 0, actual2.Length, name);
        }

        //--------
        protected static byte[] ArrayUtils_NTimes(byte[] input, int repeatCount)
        {
            byte[] copies = new byte[input.Length * 2];
            int idx = 0;
            for (int i = 0; i < repeatCount; ++i) {
                input.CopyTo(copies, idx);
                idx += input.Length;
            }
            return copies;
        }

        //--------
        public const byte SE = 240;
        public const byte SB = 250;
        public const byte WILL = 251;
        public const byte WONT = 252;
        public const byte DO = 253;
        public const byte DONT = 254;
        public const byte IAC = 255;
        //
        public const byte NOP = 241;
        public const byte AYT = 246;
        public const byte GA = 249;
        //
        public const byte OptionFooOne = 99;
        public const byte OptionFooTwo = 98;

        //--------
        public static void EnsureConsoleTraceListener()
        {
            int count = 0;
            foreach (TraceListener cur in Trace.Listeners) {
                if (cur is ConsoleTraceListener) {
                    // NUnit sometimes seems not to see the output of an old instance, so always add anew!!//++count;
                    Trace.Listeners.Remove(cur);
                    break;
                }
            }//for
            if (count == 0) {
                Trace.Listeners.Add(new ConsoleTraceListener(false));
            }
        }

    }
}
